<template>
  <div class="bom-simple-entry-page">
    <header class="page-header">
      <div class="title-wrap">
        <h2>BOM条目</h2>
        <span class="sub">维护成本组成 / 费用计算方式 </span>
      </div>
      <div class="actions">
        <div class="search-box">
          <svg class="search-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <circle cx="11" cy="11" r="8" />
            <path d="m21 21-4.35-4.35" />
          </svg>
          <input v-model="keyword" placeholder="搜索 名称/物料编码" />
        </div>
        <select v-model="filterMode" class="select">
          <option value="">费用模式(全部)</option>
          <option value="fixed">固定费用</option>
          <option value="count">计数费用</option>
        </select>
        <button class="btn primary elevate no-wrap" @click="openCreate">
          <svg class="btn-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
            <line x1="12" y1="5" x2="12" y2="19" />
            <line x1="5" y1="12" x2="19" y2="12" />
          </svg>
          新增条目
        </button>
      </div>
    </header>

    <section v-if="entries.length || loading" class="table-wrapper card">
      <table class="entry-table fancy">
        <thead>
          <tr>
            <th style="width:80px">物料分类</th>
            <th style="width:120px">物料编码</th>
            <th style="width:140px">名称</th>
            <th style="width:100px">BOM类型</th>
            <th style="width:100px">属性</th>
            <th style="width:100px">费用模式</th>
            <th style="width:140px">创建时间</th>
            <th style="width:140px">修改时间</th>
            <th style="width:110px">操作</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="e in filtered" :key="e.id">
            <td>{{ displayCategoryName(e.materialCategory) }}</td>
            <td>{{ e.materialCode }}</td>
            <td>{{ e.costName }}</td>
            <td>{{ formatBomType(e.bomType) }}</td>
            <td class="attr-cell">
              <button type="button" class="btn view sm" @click="openAttributeViewer(e)" title="查看属性" aria-label="查看属性">
                <span class="icon" aria-hidden="true">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" width="16" height="16">
                    <circle cx="12" cy="12" r="3" />
                    <path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12Z" />
                  </svg>
                </span>
                <span class="txt">查看</span>
              </button>
            </td>
            <td class="mode-cell">
              <button class="btn view sm" @click="openModeViewer(e)" title="查看费用模式" aria-label="查看费用模式">
                <span class="eye" aria-hidden="true">
                  <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" width="14" height="14">
                    <rect x="3" y="4" width="18" height="16" rx="2" ry="2" />
                    <line x1="3" y1="10" x2="21" y2="10" />
                    <line x1="8" y1="4" x2="8" y2="20" />
                    <line x1="14" y1="4" x2="14" y2="20" />
                  </svg>
                </span>
                <span class="txt">查看</span>
              </button>
            </td>
            <td>{{ formatDate(e.createdAt) }}</td>
            <td>{{ formatDate(e.updatedAt) }}</td>
            <td class="ops">
              <button class="btn ghost sm blue" @click="edit(e)">编辑</button>
            </td>
          </tr>
          <tr v-if="!loading && !filtered.length"><td colspan="9" class="empty-tip" style="padding:2rem 0">本页暂无数据</td></tr>
          <tr v-if="loading"><td colspan="9" class="loading-row">加载中...</td></tr>
        </tbody>
      </table>
      <div class="pagination-bar" v-if="total > 0 && !loading">
        <button type="button" class="btn ghost sm" :disabled="page===1" @click="goPrev">上一页</button>
        <span class="pager-info">第 {{ page }} / {{ totalPages }} 页 (共 {{ total }} 条)</span>
        <button type="button" class="btn ghost sm" :disabled="page===totalPages" @click="goNext">下一页</button>
      </div>
    </section>
    <p v-else class="empty-tip">暂无条目，请新增或调整搜索条件。</p>

    <BomSimpleEntryDialog
      ref="entryDialogRef"
      @save="onDialogSave"
      @request-attr-view="onAttrView"
      @request-attr-edit="onAttrEdit"
    />
    <SimpleCostModeViewer ref="modeViewerRef" />
    <AttributeViewer ref="attrViewerRef" />
    <DynamicAttributeDialog ref="dynAttrDialogRef" />
  </div>
</template>

<script setup lang="ts">
import { ref, computed, watch, onBeforeUnmount, nextTick } from 'vue';
import { useRoute } from 'vue-router';
import BomSimpleEntryDialog from './components/BomSimpleEntryDialog.vue';
import SimpleCostModeViewer from './components/SimpleCostModeViewer.vue';
import AttributeViewer from './components/AttributeViewer.vue';
import DynamicAttributeDialog from './components/DynamicAttributeDialog.vue';
import { useProductsForBom } from '../../store';
import { buildAttributeSpec, type MaterialAttributeDefinition } from './mock/materialAttributes';
import { getMaterialClass } from '../../services/menu';
import { fetchBomList, editBom, buildBomEditBody } from '../../services/bom';
import type { BomRecordApi } from '../../types/bom';

interface SimpleBomEntry {
  id: string;
  classId?: string | number; // 后端的物料分类 Id，编辑时需要传递
  costName: string;
  bomType: 'product' | 'package';
  productName?: string;
  productType?: string;
  materialCode: string;
  materialCategory: string; // 分类编码
  costMode: 'fixed' | 'count';
  useFormula?: boolean;
  fixedAmount?: number;
  unitPrice?: number;
  // 属性：默认规格（例：100×200）后续可扩展为对象；此处简单存字符串
  attributeSpec?: string;
  attributeDefinitions?: MaterialAttributeDefinition[];
  attributeValues?: Record<string, any>;
  createdAt: number;
  updatedAt: number;
}

const productOptions = useProductsForBom();

const keyword = ref('');
const filterMode = ref('');
const route = useRoute();

// 远程数据
const entries = ref<SimpleBomEntry[]>([]);
const loading = ref(false);
const page = ref(1);
const pageSize = ref(10);
const total = ref(0);
const totalPages = computed(() => Math.max(1, Math.ceil(total.value / pageSize.value)));
let syncingFromResponse = false;

function mapApiRecord(r: BomRecordApi): SimpleBomEntry {
  // 将后端 attributes 转换为 definitions / values，并补充分组、排序、单位等信息
  const attributeDefinitions: MaterialAttributeDefinition[] | undefined = r.attributes?.map(a => {
    const name = a.propertyName;
    const code = name.replace(/\s+/g,'_');
    const unitMatch = name.match(/\((mm|cm|m)\)/i);
    const unit = unitMatch ? unitMatch[1] : undefined;
    const isSize = /长度|宽度|高度|厚度|length|width|height|thickness/i.test(name);
    const isRemark = /备注|说明|remark|memo|note|desc/i.test(name);
    const rawVal = a.propertyValue;
    const numericLike = rawVal != null && /^\d+(\.\d+)?$/.test(String(rawVal));
    return {
      code,
      name,
      control: (isSize && numericLike) ? 'NumericTextBox' : 'TextBox',
      group: isSize ? 'size' : (isRemark ? 'remark' : 'basic'),
      //unit,
      sortId: typeof a.sort === 'number' ? a.sort : undefined,
    } as any as MaterialAttributeDefinition;
  });
  const attributeValues: Record<string, any> | undefined = attributeDefinitions?.reduce((acc, d) => {
    const src = r.attributes?.find(a => a.propertyName === d.name);
    acc[d.code] = src?.propertyValue ?? '';
    return acc;
  }, {} as Record<string, any>);
  return {
    id: r.id,
    classId: r.classId,
    costName: r.name || r.invName || '-',
    bomType: (r.bomType as any) || 'product',
    productName: r.invName || r.name,
    productType: r.productType,
    materialCode: r.invCode || '-',
    materialCategory: r.className || '-',
    costMode: (r.feeModel as any) === 'count' ? 'count' : 'fixed',
    fixedAmount: (r.feeModel === 'fixed') ? Number(r.feeAmount) : undefined,
    unitPrice: (r.feeModel === 'count') ? Number(r.feeAmount) : undefined,
    createdAt: r.createTime ? Date.parse(r.createTime.replace(/-/g,'/')) : Date.now(),
    updatedAt: r.updateTime ? Date.parse(r.updateTime.replace(/-/g,'/')) : Date.now(),
    attributeDefinitions,
    attributeValues,
    attributeSpec: attributeDefinitions && attributeValues ? buildAttributeSpec(r.className || '', attributeValues, attributeDefinitions) : undefined
  };
}

async function loadData() {
  loading.value = true;
  try {
    const className = (route.query.category as string) || '';
    const productType = (route.query.prod as string) || '';
    const kw = keyword.value.trim();
    // 如果 kw 为纯数字（可能含空格已被 trim），则优先作为 invCode；
    // 如果包含中文字符，则作为 invName；否则同时传入 invCode 和 invName 以扩大匹配范围。
    // 规则说明：
    // - 纯数字：/^[0-9]+$/ 匹配
    // - 包含中文：/\p{Script=Han}/u 匹配（需要支持 Unicode 属性）
    let invCodeParam = '';
    let invNameParam = '';
    if (kw) {
      const isNumeric = /^[0-9]+$/.test(kw);
      const hasChinese = /\p{Script=Han}/u.test(kw);
      if (isNumeric) {
        invCodeParam = kw;
      } else if (hasChinese) {
        invNameParam = kw;
      } else {
        // 既不是纯数字也不包含中文，保守地传给两个字段以提高命中率
        invCodeParam = kw;
        invNameParam = kw;
      }
    }

    const requestedPage = page.value;
    const data = await fetchBomList({
      page: page.value,
      pageSize: pageSize.value,
      invCode: invCodeParam,
      invName: invNameParam,
      className: className && className !== '全部' ? className : '',
      productType: productType && productType !== '全部' ? productType : ''
    });
    // 兼容后端分页字段命名：size/current 或 page/pageSize
    total.value = data.total ?? total.value;
    const resolvedSize = data.pageSize ?? data.size;
    const resolvedPage = data.page ?? data.current;
    syncingFromResponse = true;
    if (typeof resolvedSize === 'number' && resolvedSize > 0) {
      pageSize.value = resolvedSize;
    }
    if (typeof resolvedPage === 'number' && resolvedPage >= 1) {
      page.value = resolvedPage;
    } else {
      page.value = requestedPage;
    }
    await nextTick();
    syncingFromResponse = false;
    entries.value = (data.records || []).map(mapApiRecord);
  } catch (err) {
    console.error('加载BOM失败', err);
    entries.value = [];
    syncingFromResponse = false;
  } finally {
    loading.value = false;
  }
}

// 仅对非关键字的过滤条件立即触发加载（page/filter/route 变化）
watch([filterMode, () => route.query.category, () => route.query.prod], () => {
  if (page.value !== 1) {
    page.value = 1;
  } else {
    loadData();
  }
});
watch([page, pageSize], () => {
  if (syncingFromResponse) return;
  loadData();
});

// 对 keyword 使用防抖，避免频繁请求。用户停止输入 400ms 后触发。
let keywordTimer: number | null = null;
watch(keyword, () => {
  // 修改关键字时重置页码
  page.value = 1;
  if (keywordTimer) window.clearTimeout(keywordTimer);
  keywordTimer = window.setTimeout(() => {
    loadData();
    keywordTimer = null;
  }, 1000);
});

onBeforeUnmount(() => {
  if (keywordTimer) window.clearTimeout(keywordTimer);
});

// 首次加载
loadData();

// 已移除 enrichDemoAttributes：不再使用假数据填充属性；依赖后端真实返回。

const filtered = computed(() => {
  // 远程已分页和粗过滤，这里仅按费用模式二次过滤
  if (!filterMode.value) return entries.value;
  return entries.value.filter(e => e.costMode === filterMode.value);
});

const entryDialogRef = ref<InstanceType<typeof BomSimpleEntryDialog> | null>(null);
const current = ref<SimpleBomEntry | null>(null); // 仅用于属性查看引用
const modeViewerRef = ref<InstanceType<typeof SimpleCostModeViewer> | null>(null);
const attrViewerRef = ref<InstanceType<typeof AttributeViewer> | null>(null);
const dynAttrDialogRef = ref<InstanceType<typeof DynamicAttributeDialog> | null>(null);

// 分类选项仅用于表格展示分类名称
const categoryOptions = ref<{ code: string; name: string }[]>([]);
getMaterialClass().then(list => {
  categoryOptions.value = (list || []).map(item => ({ code: String(item.Id), name: item.MaterialClass }));
}).catch(() => { categoryOptions.value = []; });
function displayCategoryName(code: string) { return categoryOptions.value.find(c => c.code === code)?.name || code; }

function openCreate() { entryDialogRef.value?.open(); }
function edit(e: SimpleBomEntry) { current.value = e; entryDialogRef.value?.open(e); }

function goPrev(){ if(page.value>1){ page.value -= 1; } }
function goNext(){ if(page.value < totalPages.value){ page.value += 1; } }

async function onDialogSave(payload: any) {
  // 统一处理：不直接写回前端 entries，而是调用后端并重新拉取，避免页面与后台数据不一致
  if (!payload) return;
  const isEdit = !!payload.id && entries.value.some(e => e.id === payload.id);
  try {
    if (isEdit) {
      const editBody = buildBomEditBody(payload);
      await editBom(editBody);
    } else {
      // 新增场景依旧由弹窗组件内部调用 /api/bom/add，这里只需刷新列表即可
      // payload 已由弹窗返回的临时对象（可能含 __duplicateName 标记），不写入 entries 避免不一致
    }
  } catch (err) {
    console.error('保存BOM失败', err);
  } finally {
    // 成功或失败都尝试刷新，确保页面反映后端最新状态
    await loadData();
  }
}

// 处理属性查看：直接调用属性查看弹窗
function onAttrView(payload: { attributeSpec?:string; definitions?:any[]; values?:Record<string,any>; categoryCode?:string; materialName?:string }) {
  attrViewerRef.value?.open({
    id: current.value?.id || 'tmp',
    attributeSpec: payload.attributeSpec,
    attributeDefinitions: payload.definitions,
    attributeValues: payload.values,
    categoryCode: payload.categoryCode
  });
}

// 处理属性编辑：打开动态属性编辑对话框并将结果写回正在编辑中的 new entry（通过子组件 form 暂存已在子组件内部）。由于子组件未暴露 form，这里暂不直接写回；本次仅演示打开编辑器，后续可通过事件回传更新。
async function onAttrEdit(payload: { categoryCode:string; definitions:any[]; values:Record<string,any>; materialName?:string }) {
  const result = await dynAttrDialogRef.value?.open({
    categoryCode: payload.categoryCode,
    categoryName: displayCategoryName(payload.categoryCode),
    materialName: payload.materialName || '',
    definitions: payload.definitions,
    values: payload.values
  });
  // 交互：将结果通过再次打开 attr viewer（可选）展示
  if(result?.values){
    // 更新子对话框属性并自动生成名称；不再自动弹出属性查看弹窗，保持与产品详情页行为一致
    entryDialogRef.value?.updateAttributes(result.values, result.spec);
    // 若需要查看，用户可在列表中点击“查看”按钮或在编辑模式下使用查看属性入口
  }
}

function openModeViewer(e: SimpleBomEntry) {
  modeViewerRef.value?.open({
    id: e.id,
    costMode: e.costMode,
    fixedAmount: e.costMode === 'fixed' ? e.fixedAmount : undefined,
    unitPrice: e.costMode === 'count' ? e.unitPrice : undefined
  });
}
// 仅查看属性弹窗：接受完整条目或局部属性对象
type AttributeViewPayload = Pick<SimpleBomEntry,'id' | 'attributeSpec' | 'attributeDefinitions' | 'attributeValues' | 'materialCategory'>;
function openAttributeViewer(e: SimpleBomEntry | AttributeViewPayload) {
  attrViewerRef.value?.open({
    id: e.id,
    attributeSpec: e.attributeSpec,
    attributeDefinitions: (e as any).attributeDefinitions,
    attributeValues: (e as any).attributeValues,
    categoryCode: (e as any).materialCategory
  });
}

function formatBomType(t?: 'product' | 'package') { return t === 'package' ? '包装BOM' : '物料BOM'; }
function formatDate(ts: number) { const d = new Date(ts); return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`; }
function toCurrency(v?: number) { if (v==null) return '-'; return v.toFixed(2); }
</script>

<style scoped>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
* { font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI','Roboto',sans-serif; }
.bom-simple-entry-page { display:flex; flex-direction:column; gap:1.2rem; animation: pageSlideIn .6s cubic-bezier(.16,1,.3,1); }
@keyframes pageSlideIn { from {opacity:0; transform: translateY(20px);} to {opacity:1; transform:translateY(0);} }
.page-header { display:flex; flex-wrap:wrap; justify-content:space-between; align-items:flex-end; gap:1rem; padding:1rem 1.25rem 1.1rem; background:linear-gradient(145deg,#ffffff,#f8fafc); border:1px solid #e2e8f0; border-radius:16px; box-shadow:0 4px 20px -4px rgba(15,23,42,.15),0 2px 8px -2px rgba(15,23,42,.1); position:relative; overflow:hidden; }
.page-header:before { content:''; position:absolute; top:0; left:0; right:0; height:3px; background:linear-gradient(90deg,#3b82f6,#8b5cf6,#06b6d4); opacity:.8; }
.title-wrap { display:flex; flex-direction:column; gap:.4rem; }
.title-wrap h2 { margin:0; font-size:24px; font-weight:700; letter-spacing:-.025em; color:#0f172a; background:linear-gradient(135deg,#1e293b,#475569); background-clip:text; -webkit-background-clip:text; -webkit-text-fill-color:transparent; }
.title-wrap .sub { font-size:13px; color:#64748b; letter-spacing:.01em; font-weight:500; }
.actions { display:flex; gap:.75rem; align-items:center; flex-wrap:wrap; }
.search-box { display:flex; align-items:center; gap:.5rem; padding:.6rem .8rem; border:1px solid #cbd5e1; border-radius:12px; background:#fff; min-width:260px; transition:all .3s cubic-bezier(.4,0,.2,1); position:relative; }
.search-box:focus-within { border-color:#3b82f6; box-shadow:0 0 0 4px rgba(59,130,246,.15),0 4px 12px -2px rgba(59,130,246,.2); transform:translateY(-1px); }
.search-icon { width:18px; height:18px; color:#6b7280; transition:color .3s; }
.search-box:focus-within .search-icon { color:#3b82f6; }
.search-box input { border:none; outline:none; font-size:14px; flex:1; font-weight:500; color:#374151; }
.search-box input::placeholder { color:#9ca3af; font-weight:400; }
.select { padding:.6rem .8rem; border:1px solid #cbd5e1; border-radius:12px; background:#fff; font-size:14px; min-width:160px; transition:all .3s cubic-bezier(.4,0,.2,1); font-weight:500; }
.select:focus { border-color:#3b82f6; box-shadow:0 0 0 4px rgba(59,130,246,.15); transform:translateY(-1px); }
.btn { position:relative; cursor:pointer; border:1px solid transparent; padding:.65rem 1.2rem; border-radius:12px; font-size:14px; font-weight:600; letter-spacing:.025em; line-height:1; display:inline-flex; align-items:center; gap:.5rem; transition:all .3s cubic-bezier(.4,0,.2,1); overflow:hidden; }
.btn.no-wrap { white-space: nowrap; }
.btn.primary { background:linear-gradient(135deg,#3b82f6,#2563eb); color:#fff; border-color:#2563eb; }
.btn.primary:hover { background:linear-gradient(135deg,#2563eb,#1d4ed8); transform:translateY(-2px); }
.btn.primary:active { transform:translateY(1px) scale(.98); }
.btn.ghost { background:rgba(255,255,255,.8); border:1px solid #e2e8f0; color:#374151; }
.btn.ghost:hover { background:#f8fafc; border-color:#cbd5e1; transform:translateY(-1px); }
.btn.ghost.danger { color:#dc2626; }
.btn.ghost.danger:hover { background:#fef2f2; border-color:#fecaca; }
.btn.ghost.blue { color:#2563eb; }
.btn.ghost.blue:hover { background:#eff6ff; border-color:#bfdbfe; }
.btn.sm { padding:.5rem .8rem; font-size:13px; border-radius:10px; }
.btn.elevate { box-shadow:0 4px 14px -2px rgba(59,130,246,.4),0 2px 6px -1px rgba(59,130,246,.2); }
.btn.elevate:hover { box-shadow:0 8px 25px -3px rgba(59,130,246,.4),0 4px 12px -2px rgba(59,130,246,.3); }
.btn.view { position:relative; background:linear-gradient(135deg,#ffffff,#f8fafc); border:1px solid #e2e8f0; color:#374151; padding:.5rem .9rem; border-radius:20px; overflow:hidden; }
.btn.view .eye svg { display:block; }
.btn.view .txt { font-size:12px; font-weight:600; letter-spacing:.025em; }
.btn.view:before { content:""; position:absolute; inset:0; background:radial-gradient(circle at 30% 30%, rgba(59,130,246,.15), transparent 60%); opacity:0; transition:opacity .4s; }
.btn.view:hover:before { opacity:1; }
.btn.view:hover { border-color:#3b82f6; color:#1e40af; box-shadow:0 4px 12px -2px rgba(59,130,246,.3); }
.table-wrapper.card { border:1px solid #e2e8f0; border-radius:16px; background:#fff; box-shadow:0 8px 25px -6px rgba(15,23,42,.1),0 3px 10px -2px rgba(15,23,42,.05); padding:.5rem .8rem .9rem; overflow:hidden; animation: tableSlideIn .8s cubic-bezier(.16,1,.3,1) .2s both; }
@keyframes tableSlideIn { from {opacity:0; transform:translateY(30px);} to {opacity:1; transform:translateY(0);} }
.entry-table { width:100%; border-collapse:separate; border-spacing:0; font-size:14px; }
.entry-table.fancy thead th { background:linear-gradient(135deg,#f8fafc,#f1f5f9); font-weight:700; font-size:12px; letter-spacing:.05em; text-transform:uppercase; color:#475569; position:sticky; top:0; z-index:1; border-bottom:2px solid #e2e8f0; }
.entry-table th,.entry-table td { padding:.8rem .9rem; text-align:left; }
.entry-table tbody tr { border-radius:8px; transition:all .3s cubic-bezier(.4,0,.2,1); animation: tableRowSlideIn .6s cubic-bezier(.16,1,.3,1) both; }
@keyframes tableRowSlideIn { from {opacity:0; transform:translateX(-20px);} to {opacity:1; transform:translateX(0);} }
.entry-table tbody tr:hover { background:linear-gradient(135deg,#f8fafc,#f1f5f9); transform:translateX(4px); box-shadow:0 4px 12px -2px rgba(59,130,246,.15); border-left:3px solid var(--primary-500); }
.entry-table tbody tr + tr td { border-top:1px solid #f1f5f9; }
.ops { display:flex; gap:.5rem; }
.empty-tip { color:#64748b; text-align:center; padding:3rem 0; font-size:16px; font-weight:500; }
.pagination-bar { display:flex; align-items:center; gap:.6rem; justify-content:flex-end; padding:.6rem .4rem .2rem; }
.pager-info { font-size:12px; color:#475569; font-weight:600; }
.loading-row { text-align:center; font-size:14px; font-weight:600; color:#475569; }
/* 弹窗样式已移动到组件 BomSimpleEntryDialog */

/* Section cards */
.form-section-card { border:1px solid #e2e8f0; background:linear-gradient(135deg,#ffffff,#f8fafc); border-radius:18px; padding:1.05rem 1.15rem 1.2rem; box-shadow:0 6px 18px -6px rgba(15,23,42,.15),0 2px 6px -2px rgba(15,23,42,.06); display:flex; flex-direction:column; gap:1rem; position:relative; }
.form-section-card:hover { box-shadow:0 10px 28px -10px rgba(15,23,42,.25),0 4px 14px -4px rgba(15,23,42,.15); }
.section-title-row { display:flex; flex-wrap:wrap; align-items:center; gap:.8rem; }
.section-title { margin:0; font-size:15px; font-weight:700; letter-spacing:.02em; position:relative; padding-left:.85rem; line-height:1.15; }
.section-title:before { content:""; position:absolute; left:0; top:50%; transform:translateY(-50%); width:4px; height:70%; background:linear-gradient(135deg,#3b82f6,#8b5cf6); border-radius:2px; }
.section-sub { margin:0; font-size:12px; color:#64748b; font-weight:500; line-height:1.4; flex:1 1 auto; }

/* Base info grid */
.grid-two { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:1rem 1.2rem; }
.field-block { display:flex; flex-direction:column; gap:.45rem; font-size:13px; font-weight:600; color:#374151; }
.field-block input,.field-block select { padding:.75rem .9rem; border:1px solid #d1d5db; border-radius:12px; font-size:14px; background:#fff; transition:all .25s cubic-bezier(.4,0,.2,1); font-weight:500; }
.field-block input:focus,.field-block select:focus { border-color:#3b82f6; box-shadow:0 0 0 4px rgba(59,130,246,.12); outline:none; transform:translateY(-1px); }
.field-block input.readonly { background:#f1f5f9; cursor:not-allowed; opacity:.75; }

/* Mode radios */
.mode-radios { display:flex; gap:.8rem; flex-wrap:wrap; }
.pill-radio { border:2px solid #e5e7eb; background:#fff; padding:.7rem 1.15rem; border-radius:26px; font-size:13px; cursor:pointer; line-height:1; box-shadow:0 2px 4px rgba(0,0,0,.05); transition:all .35s cubic-bezier(.4,0,.2,1); font-weight:600; letter-spacing:.025em; position:relative; }
.pill-radio:hover { background:#f8fafc; border-color:#cbd5e1; transform:translateY(-2px); }
.pill-radio.active { background:linear-gradient(135deg,#3b82f6,#2563eb); color:#fff; border-color:#2563eb; box-shadow:0 4px 12px -1px rgba(59,130,246,.4); }
.pill-radio.count.active { background:linear-gradient(135deg,#fef3c7,#fde68a); border-color:#fcd34d; box-shadow:0 4px 14px -2px rgba(245,158,11,.45),0 2px 6px -1px rgba(245,158,11,.35); color:#92400e; }
.pill-radio.count.active:hover { background:linear-gradient(135deg,#fde68a,#fcd34d); border-color:#fbbf24; }
.pill-radio.count.active:active { background:linear-gradient(135deg,#fcd34d,#fbbf24); transform:translateY(1px) scale(.97); }
.pill-radio.fixed.active { background:linear-gradient(135deg,#3b82f6,#2563eb); }

/* New cost config horizontal bar */
.cost-config-bar { display:flex; align-items:flex-start; justify-content:flex-start; gap:.75rem; flex-wrap:nowrap; position:relative; min-height:54px; }
.cost-config-bar .mode-radios { flex:0 0 auto; display:flex; gap:.6rem; }
.amount-focus.inline { position:absolute; top:0; left:66.666%; transform:translateX(-50%); min-width:170px; border-radius:14px; padding:.65rem .85rem .7rem; display:flex; flex-direction:column; align-items:flex-end; gap:.18rem; box-shadow:0 4px 14px -4px rgba(59,130,246,.28); border:1px solid transparent; }
.amount-focus.inline.mode-fixed { background:linear-gradient(135deg,#eff6ff,#ffffff); border-color:#93c5fd; }
.amount-focus.inline.mode-count { background:linear-gradient(135deg,#fef3c7,#fde68a); border-color:#fcd34d; box-shadow:0 6px 18px -6px rgba(245,158,11,.5),0 2px 8px -2px rgba(245,158,11,.35); }
.amount-focus.inline.mode-count:after { content:""; position:absolute; inset:0; border-radius:inherit; background:radial-gradient(circle at 35% 30%,rgba(245,158,11,.35),transparent 65%); opacity:.55; pointer-events:none; mix-blend-mode:multiply; }
.amount-focus.inline.mode-count .focus-label { color:#92400e; text-shadow:0 1px 0 rgba(255,255,255,.6); }
.amount-focus.inline.mode-count .focus-value { color:#92400e; text-shadow:0 1px 0 rgba(255,255,255,.5); }
.amount-focus.inline .focus-value { font-size:30px; }
.amount-focus.inline .focus-label { font-size:11px; }
.amount-focus.inline .focus-label { align-self:flex-start; font-size:10px; letter-spacing:.06em; }
.amount-row.single-line { margin-top:-.2rem; }
.amount-row.single-line .amount-field { max-width:300px; }

@media (max-width:720px) {
  .cost-config-bar { flex-direction:column; align-items:stretch; min-height:auto; }
  .amount-focus.inline { position:static; transform:none; align-self:flex-end; margin-top:.25rem; }
  .amount-row.single-line .amount-field { max-width:100%; }
}

/* Amount row */
.amount-row { margin-top:.65rem; display:flex; flex-wrap:wrap; gap:.9rem; align-items:flex-start; }
.amount-row.compact-layout { align-items:flex-start; }
.amount-field { flex:1 1 240px; }
.input-affix { position:relative; display:flex; align-items:center; }
.input-affix .prefix { position:absolute; left:.75rem; font-size:13px; color:#64748b; font-weight:600; }
.input-affix input { width:100%; padding:.75rem .9rem .75rem 1.65rem; border:1px solid #d1d5db; border-radius:12px; font-size:16px; font-weight:600; letter-spacing:.5px; background:#fff; transition:all .25s cubic-bezier(.4,0,.2,1); }
.input-affix input:focus { border-color:#3b82f6; box-shadow:0 0 0 4px rgba(59,130,246,.12); outline:none; }
.amount-focus { flex:0 0 auto; min-width:150px; background:linear-gradient(135deg,#eff6ff,#ffffff); border:1px solid #bfdbfe; border-radius:16px; padding:.65rem .85rem .7rem; display:flex; flex-direction:column; justify-content:center; align-items:center; gap:.25rem; box-shadow:0 4px 12px -2px rgba(59,130,246,.2); position:relative; margin-top:-.15rem; }
.amount-focus.tight { transform:translateY(-4px) translateX(-4px); }
.amount-focus:after { content:""; position:absolute; inset:0; border-radius:inherit; background:radial-gradient(circle at 30% 30%,rgba(59,130,246,.25),transparent 60%); opacity:.4; pointer-events:none; }
.focus-label { font-size:11px; font-weight:700; letter-spacing:.05em; color:#1e3a8a; text-transform:uppercase; }
.focus-value { font-size:28px; font-weight:700; letter-spacing:-.02em; line-height:1.05; color:#0f172a; font-variant-numeric:tabular-nums; word-break:break-all; }

/* Attr config */
.attr-config-body { display:flex; gap:1rem; flex-wrap:wrap; align-items:center; }
.attr-config-body.single { gap:.5rem; }
.attr-mini-btn { display:inline-flex; align-items:center; gap:.55rem; padding:.65rem 1rem; background:linear-gradient(135deg,#ffffff,#f8fafc); border:1px solid #cbd5e1; border-radius:12px; font-size:13px; font-weight:600; color:#374151; cursor:pointer; transition:all .3s cubic-bezier(.4,0,.2,1); box-shadow:0 2px 4px rgba(0,0,0,.06); }
.attr-mini-btn:hover { background:linear-gradient(135deg,#eff6ff,#ffffff); border-color:#3b82f6; color:#1e40af; transform:translateY(-2px); }
.attr-mini-btn.filled { background:linear-gradient(135deg,#ecfdf5,#d1fae5); border-color:#16a34a; color:#166534; }
.attr-mini-btn.filled:hover { background:linear-gradient(135deg,#dcfce7,#bbf7d0); }
/* disabled state */
.attr-mini-btn.disabled, .attr-mini-btn:disabled { opacity:.55; cursor:not-allowed; filter:grayscale(.3); transform:none !important; box-shadow:0 0 0 0 transparent; }
.attr-mini-btn.disabled:hover, .attr-mini-btn:disabled:hover { background:linear-gradient(135deg,#ffffff,#f8fafc); border-color:#cbd5e1; color:#374151; }
.attr-summary { font-size:11px; color:#64748b; font-weight:600; }
/* readonly(view-only) 状态：保留交互但视觉弱化 */
.attr-mini-btn.readonly { opacity:.85; }
.attr-mini-btn.readonly:hover { transform:translateY(-1px); }
/* 规格预览被移除 */

/* 原内联弹窗操作区样式移除 */

@media (max-width:680px) {
  .amount-row { flex-direction:column; align-items:stretch; }
  .amount-focus { width:100%; flex:1 1 auto; flex-direction:row; justify-content:space-between; padding:.85rem 1.15rem; }
  .focus-value { font-size:24px; }
  .dialog-header { padding:1rem 1.1rem .75rem; }
  .dialog-content-scroll { padding:1rem 1.1rem 1.2rem; }
  .sticky-actions { flex-direction:column; align-items:stretch; }
  .actions-btns { justify-content:flex-end; }
}
/* 关闭按钮样式由组件内维护 */
.form-grid { display:grid; grid-template-columns:repeat(auto-fill,minmax(200px,1fr)); gap:1rem; }
.form-grid label { display:flex; flex-direction:column; font-size:13px; gap:.4rem; color:#374151; font-weight:600; }
.form-grid input,.form-grid select { padding:.75rem .9rem; border:1px solid #d1d5db; border-radius:12px; font-size:14px; background:#fff; transition:all .3s cubic-bezier(.4,0,.2,1); font-weight:500; }
.form-grid input:focus,.form-grid select:focus { border-color:#3b82f6; box-shadow:0 0 0 4px rgba(59,130,246,.1); outline:none; transform:translateY(-1px); }
.form-grid input.readonly,.form-grid select.readonly { background:#f9fafb; cursor:not-allowed; opacity:.7; }
.mode-section { display:flex; flex-direction:column; gap:.8rem; background:linear-gradient(145deg, rgba(248,250,252,.8), rgba(241,245,249,.6)); border:1px solid #e2e8f0; border-radius:14px; padding:1rem 1.2rem; }
.section-header { display:flex; align-items:center; justify-content:space-between; }
.section-content { display:flex; align-items:center; gap:1.2rem; flex-wrap:wrap; }
.attr-editor-btn { display:inline-flex; align-items:center; gap:.65rem; padding:.65rem 1rem; background:linear-gradient(145deg, #ffffff, #f8fafc); border:1px solid #cbd5e1; border-radius:12px; font-size:14px; font-weight:600; color:#374151; cursor:pointer; transition:all .3s cubic-bezier(.4,0,.2,1); position:relative; box-shadow:0 2px 6px -1px rgba(0,0,0,.08), 0 1px 3px rgba(0,0,0,.06); }
.attr-editor-btn:hover { background:linear-gradient(145deg, #eff6ff, #ffffff); border-color:#3b82f6; color:#1e40af; transform:translateY(-2px); box-shadow:0 6px 16px -4px rgba(59,130,246,.3), 0 3px 8px -2px rgba(59,130,246,.2); }
.attr-editor-btn:active { transform:translateY(0) scale(.98); }
.attr-editor-btn.has-value { background:linear-gradient(145deg, #ecfdf5, #d1fae5); border-color:#22c55e; color:#166534; }
.attr-editor-btn.has-value:hover { background:linear-gradient(145deg, #dcfce7, #bbf7d0); border-color:#16a34a; color:#14532d; box-shadow:0 6px 16px -4px rgba(34,197,94,.35), 0 3px 8px -2px rgba(34,197,94,.25); }
.btn-icon-wrap { width:32px; height:32px; display:flex; align-items:center; justify-content:center; background:linear-gradient(135deg, #f1f5f9, #e2e8f0); border:1px solid #cbd5e1; border-radius:10px; box-shadow:0 2px 4px -1px rgba(0,0,0,.1); transition:all .3s ease; }
.attr-editor-btn:hover .btn-icon-wrap { background:linear-gradient(135deg, #dbeafe, #bfdbfe); border-color:#60a5fa; }
.attr-editor-btn.has-value .btn-icon-wrap { background:linear-gradient(135deg, #bbf7d0, #86efac); border-color:#4ade80; }
.attr-editor-btn.has-value:hover .btn-icon-wrap { background:linear-gradient(135deg, #a7f3d0, #6ee7b7); }
.btn-icon-wrap svg { transition:transform .4s cubic-bezier(.4,0,.2,1); }
.attr-editor-btn:hover .btn-icon-wrap svg { transform:rotate(25deg) scale(1.1); }
.btn-text { font-size:13px; letter-spacing:.02em; }
.status-indicator { position:absolute; top:-6px; right:-6px; width:20px; height:20px; background:linear-gradient(135deg, #22c55e, #16a34a); border:2px solid #fff; border-radius:50%; display:flex; align-items:center; justify-content:center; color:#fff; box-shadow:0 3px 8px -2px rgba(34,197,94,.6); animation:popIn .4s cubic-bezier(.68,-.55,.265,1.55); }
@keyframes popIn { 0% { transform:scale(.3) rotate(-180deg); opacity:0; } 100% { transform:scale(1) rotate(0deg); opacity:1; } }
.icon-btn { width:38px; height:38px; padding:0; border-radius:10px; display:inline-grid; place-items:center; }
.icon-btn svg { width:18px; height:18px; }
/* responsive */
@media (max-width:640px) { .section-content { flex-direction:column; align-items:stretch; } .attr-editor-btn { justify-content:center; } }
.field-label { margin:0; font-size:13px; font-weight:700; color:#374151; letter-spacing:.025em; }
.mode-options { display:inline-flex; gap:.8rem; flex-wrap:wrap; }
.mode-option { border:2px solid #e5e7eb; background:#fff; padding:.7rem 1.2rem; border-radius:25px; font-size:13px; cursor:pointer; line-height:1; box-shadow:0 2px 4px rgba(0,0,0,.05); transition:all .3s cubic-bezier(.4,0,.2,1); font-weight:600; letter-spacing:.025em; }
.mode-option:hover { background:#f8fafc; border-color:#cbd5e1; transform:translateY(-2px); }
.mode-option.active { background:linear-gradient(135deg,#3b82f6,#2563eb); color:#fff; border-color:#2563eb; box-shadow:0 4px 12px -1px rgba(59,130,246,.4); }
.hint-text { margin:0; font-size:12px; color:#6b7280; line-height:1.5; }
.dialog-actions { display:flex; justify-content:flex-end; gap:1rem; margin-top:.5rem; padding-top:1rem; border-top:1px solid #e5e7eb; }
.dialog-actions .btn[disabled] { opacity:.5; cursor:not-allowed; box-shadow:none; }
/* Dialog two-column layout */

</style>
